In this workshop, we’ll practice some skills for creating animated graphics with ggplot2 and gganimate…plus some other cool things.

Visit and fork the workshop repo on github for information and data (https://github.com/allisonhorst/advanced-ggplot2-gganimate).

Packages required:

You’ll need to get the development version of gganimate:

# install.packages('devtools')
devtools::install_github('thomasp85/gganimate')

An overview of gganimate terms

gganimate is a new (and evolving) package for intuitive graphics animation by Thomas Lin Pedersen. Some important terms to get us started:

My three pieces of advice for getting started (from a gganimate beginner)

library(tidyverse)
## ── Attaching packages ─────────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.1.0     ✔ purrr   0.2.5
## ✔ tibble  1.4.2     ✔ dplyr   0.7.6
## ✔ tidyr   0.8.1     ✔ stringr 1.3.1
## ✔ readr   1.1.1     ✔ forcats 0.3.0
## ── Conflicts ────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(gganimate)
library(ggrepel)
library(gghighlight)

1. Getting started: Channel Island Fox population on Santa Rosa Island

  1. Data (ci_fox_pop.csv): Friends of the Island Fox (http://www1.islandfox.org/2014/)
# Get data:
ci_fox_pop <- read_csv("ci_fox_pop.csv")
## Parsed with column specification:
## cols(
##   year = col_integer(),
##   san_miguel = col_integer(),
##   santa_rosa = col_integer(),
##   santa_cruz = col_integer(),
##   santa_catalina = col_integer(),
##   san_clemente = col_integer(),
##   san_nicolas = col_integer()
## )
# Look at it:
View(ci_fox_pop)

# Gather it:
fox <- ci_fox_pop %>% 
  gather(island, pop, san_miguel:san_nicolas)

# Make a static plot frst! 
ggplot(fox, aes(x = year, y = pop)) +
  geom_point(size = 3, aes(color = island)) +
  theme_dark() +
  scale_colour_manual(values = c("white", "yellow","orange","magenta", "aquamarine","olivedrab1")) +
  labs(x = "Year", y = "Fox Population", title = "Channel Island Fox Recovery") +
  scale_y_continuous(expand = c(0,0), limits = c(0,2000)) +
  scale_x_continuous(expand = c(0,0), limits = c(1994, 2016)) +
  facet_wrap(~year)# yes, this makes no sense - but this is what we want. All of the different states are plotting separately. That's good! We're going to combine them using gganimate transitions. 
## Warning: Removed 2 rows containing missing values (geom_point).

  1. Now let’s made an animated version using gganimate functions:
# Make an animated version (remove facet_wrap, instead add animation) - 30 seconds

# Fun fact while rendering (Wikipedia): R was created by Ross Ihaka and Robert Gentleman at the University of Auckland, New Zealand, R is named partly after the first names of the first two R authors and partly as a play on the name of S. The project was conceived in 1992, with an initial version released in 1995 and a stable beta version in 2000.

ggplot(fox, aes(x = year, y = pop)) +
  geom_point(size = 3, aes(color = island)) +
  theme_dark() +
  scale_colour_manual(values = c("white", "yellow","orange","magenta", "aquamarine","olivedrab1")) +
  labs(x = "Year", y = "Fox Population", title = "Channel Island Fox Recovery") +
  scale_y_continuous(expand = c(0,0), limits = c(0,2000)) +
  scale_x_continuous(expand = c(0,0), limits = c(1994, 2016)) +
  transition_states(states = year, transition_length = 1, state_length = 1, wrap = FALSE)

  1. Changing functions (ease_aes) between states, and adding a wake:

A fun thing to explore while rendering: https://easings.net/

ggplot(fox, aes(x = year, y = pop)) +
  geom_point(size = 3, aes(color = island)) +
  theme_dark() +
  scale_colour_manual(values = c("white", "yellow","orange","magenta", "aquamarine","olivedrab1")) +
  labs(x = "Year", y = "Fox Population", title = "Channel Island Fox Recovery") +
  scale_y_continuous(expand = c(0,0), limits = c(0,2000)) +
  scale_x_continuous(expand = c(0,0), limits = c(1994, 2016)) +
  transition_states(states = year, transition_length = 1, state_length = 1, wrap = FALSE) +
  ease_aes('cubic-in-out') +
  shadow_wake(wake_length = 0.2) 

  1. shadow_wake for point permanence
# Also try shadow_mark to leave a point:

ggplot(fox, aes(x = year, y = pop)) +
  geom_point(size = 3, aes(color = island)) +
  theme_dark() +
  scale_colour_manual(values = c("white", "yellow","orange","magenta", "aquamarine","olivedrab1")) +
  labs(x = "Year", y = "Fox Population", title = "Channel Island Fox Recovery") +
  scale_y_continuous(expand = c(0,0), limits = c(0,2000)) +
  scale_x_continuous(expand = c(0,0), limits = c(1994, 2016)) +
  transition_states(states = year, transition_length = 1, state_length = 1, wrap = FALSE) +
  ease_aes('cubic-in-out') +
  shadow_mark()

  1. A line graph with transition_reveal():
# And now with a line graph (transition_reveal):

ggplot(fox, aes(x = year, y = pop)) +
  geom_line(size = 1, aes(color = island)) +
  theme_dark() +
  scale_colour_manual(values = c("white", "yellow","orange","magenta", "aquamarine","olivedrab1")) +
  labs(x = "Year", y = "Fox Population", title = "Channel Island Fox Recovery") +
  scale_y_continuous(expand = c(0,0), limits = c(0,2000)) +
  scale_x_continuous(expand = c(0,0), limits = c(1994, 2016)) +
  transition_reveal(id = island, along = year) + # really wants two things...
  ease_aes('quadratic-in-out')

  1. Adding labels
ggplot(fox, aes(x = year, y = pop, group = island)) +
  geom_line(size = 1, aes(color = island)) +
  geom_text(aes(label = island), nudge_x = 3, color = "white") +
  theme_dark() +
  scale_colour_manual(values = c("white", "yellow","orange","magenta", "aquamarine","olivedrab1")) +
  labs(x = "Year", y = "Fox Population", title = "Channel Island Fox Recovery") +
  scale_y_continuous(expand = c(0,0), limits = c(0,2000)) +
  scale_x_continuous(expand = c(0,0), limits = c(1994, 2016)) +
  transition_reveal(id = island, along = year) + # really wants two things...
  ease_aes('quadratic-in-out')

2. An animation break: ggrepel and gghighlight with Star Wars characters

  1. The ‘starwars’ dataset exists in dplyr (part of the tidvyerse), with data from the Star Wars API (https://swapi.co/). First, look at the data.
View(starwars)
  1. Filter to only include data for species: human, droid, wookiee, ewok. Relevel species with forcats’ fct_relevel
sw <- starwars %>% 
  filter(species == "Human" | species == "Droid" | species == "Wookiee" | species == "Ewok") %>% 
  mutate(species = factor(species))

sw$species <- fct_relevel(sw$species, "Ewok","Droid","Human","Wookiee")
  1. Remember the first step I recommend for gganimate: make a static version first, with different states (in this case, species) separated using facet_wrap:

Another thing included here: geom_text_repel (from ggrepel) - for “repulsive textual annotations” (seriously, see the documentation with ?geom_text_repel)

# Static version: 

ggplot(sw, aes(x = height, y = mass, label = name)) +
  geom_point(aes(color = species)) + 
  geom_text_repel(size = 2, segment.color = "gray60", segment.size = 0.2) +
  scale_color_manual(values = c("orange","navyblue","magenta","forestgreen")) +
  theme_bw()# Yeah, looks bad but this is what we want! 
## Warning: Removed 14 rows containing missing values (geom_point).
## Warning: Removed 14 rows containing missing values (geom_text_repel).

  1. Now that the static version is working, make an animated version using gganimate tools

Discrete frames: no interpolation between values from frame to frame Use transition_manual(frames = ??)

# Animated version (copied from above, minus facet_wrap): 

sw_graph <- ggplot(sw, aes(x = height, y = mass, label = name)) +
  geom_point(aes(color = species), size = 3) + 
  geom_text_repel(size = 3, segment.color = "gray60", segment.size = 0.2) + 
  scale_color_manual(values = c("orange","navyblue","magenta","forestgreen")) +
  theme_bw() +
  transition_manual(frames = species) # No tweening! That makes sense if you don't have a logical path between states. 

sw_graph
## nframes and fps adjusted to match transition

# Rendering so you can send this to all your friends:
# animate(sw_graph, nframes = 4, renderer = gifski_renderer("sw_graph.gif"))

But sometimes we have data where interpolating (tweening) between states does make sense, for example with time series data where the same factor levels exist in each state. Let’s consider another example:

4. Tweening with chick weights!

The dataset ‘ChickWeight’ exists in base R. Use ?ChickWeight for more information.

# Animated column plot:

ggplot(ChickWeight, aes(x = Chick, y = weight)) +
  geom_col(aes(fill = Diet)) +
  labs(title = "Age (days): {closest_state}") +
  scale_fill_manual(values = c("yellow","orange","coral","magenta")) +
  scale_y_continuous(expand = c(0,0)) +
  theme_dark() +
  transition_states(Time, transition_length = 3, state_length = 1)

# Find mean weight by time for each feed type
mean_wt <- ChickWeight %>% 
  group_by(Diet, Time) %>% 
  summarize(
    mean_wt = mean(weight)
  )

ggplot(mean_wt, aes(x = Time, y = mean_wt, label = Diet)) +
  geom_line(aes(color = Diet)) +
  geom_text(nudge_x = 0.2, nudge_y = 5) +
  theme_light() +
  scale_color_manual(values = c("dodgerblue","green3","purple","orange")) +
  transition_reveal(Diet, Time)